package com.ynding.cloud.auth.authentication.service.impl;

import com.ynding.cloud.auth.api.admin.api.IResourceApiService;
import com.ynding.cloud.auth.api.authentication.model.bo.AccessToken;
import com.ynding.cloud.auth.authentication.config.security.service.UserDetailsServiceImpl;
import com.ynding.cloud.auth.authentication.service.IAuthenticationService;
import com.ynding.cloud.common.dict.CloudServiceInfo;
import com.ynding.cloud.common.model.bo.AuthConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.*;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
@Service
@Slf4j
public class AuthenticationServiceImpl implements IAuthenticationService {

    /**
     * 未在资源库中的URL默认标识
     */
    public static final String NONEXISTENT_URL = "ROLE_LOGIN";

    @DubboReference
    private IResourceApiService resourceApiService;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private DefaultTokenServices tokenServices;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private AuthenticationManager authenticationManager;
    /**
     * 使用此服务要打开 ribbon.restclient.enabled=true
     */
    @Autowired
    @LoadBalanced
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    /**
     * @param authRequest 访问的url,method
     * @return 有权限true, 无权限或全局资源中未找到请求url返回否
     */
    @Override
    public boolean decide(HttpServletRequest authRequest) {
        log.debug("正在访问的url是:{}，method:{}", authRequest.getServletPath(), authRequest.getMethod());
        //获取用户认证信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //获取此url，method访问对应的权限资源信息
        List<ConfigAttribute> urlConfigAttribute = resourceApiService.getAttributes(authRequest.getServletPath());
        if (urlConfigAttribute.size() == 1 && NONEXISTENT_URL.equals(urlConfigAttribute.get(0).getAttribute())) {
            log.debug("url未在资源池中找到，拒绝访问");
        }
        //获取此访问用户所有角色拥有的权限资源
        Set<String> userResources = resourceApiService.findResourcesByUsername(authentication.getName());
        //用户拥有权限资源 与 url要求的资源进行对比
        List<String> attributes = urlConfigAttribute.stream().map(ConfigAttribute::getAttribute).collect(Collectors.toList());
        return isMatch(attributes, userResources);
    }

    @Override
    public boolean auth(String authorization, String url, String method) {
        String access_token = authorization.substring(AuthConstants.JWT_TOKEN_PREFIX.length());
        OAuth2Authentication oAuth2Authentication = tokenServices.loadAuthentication(access_token);
        Collection<GrantedAuthority> authorities = oAuth2Authentication.getAuthorities();
        //获取此token 对应的权限资源信息
        Set<String> roles = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
        //获取此url，method访问对应的权限资源信息
        List<ConfigAttribute> urlConfigAttribute = resourceApiService.getAttributes(url);
        List<String> attributes = urlConfigAttribute.stream().map(ConfigAttribute::getAttribute).collect(Collectors.toList());
        // 对比
        boolean result = roles.stream().anyMatch(role -> attributes.contains(role));
        return result;
    }

    @Override
    public boolean invalidJwtAccessToken(String authentication) {
        OAuth2AccessToken auth2AccessToken = tokenServices.readAccessToken(authentication);
        return auth2AccessToken.isExpired();
    }

    @Override
    public String getToken(String username, String password, String clientId, String clientSecret) {
        ServiceInstance serviceInstance = loadBalancerClient.choose(CloudServiceInfo.AUTH_SERVER_AUTHENTICATION);
        URI uri = serviceInstance.getUri();
        String tokenUrl = uri + "/oauth/token";
//        String tokenUrl = "http://localhost:10402/oauth/token";
        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
        // Perform the security
        final Authentication authentication = authenticationManager.authenticate(upToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        // Reload password post-security so we can generate token
        final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        // 持久化的redis
        HttpHeaders headers = new HttpHeaders();
        headers.setBasicAuth(clientId, clientSecret);
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        // 设置请求体
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("username", username);
        map.add("password", password);
        map.add("grant_type", "password");
        map.add("scope", "read write");
        // 用HttpEntity封装整个请求报文
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
        ResponseEntity<AccessToken> response = restTemplate.exchange(tokenUrl, HttpMethod.POST, request, AccessToken.class);
        String token = response.getBody().getAccess_token();
        redisTemplate.opsForValue().set(token, userDetails.getUsername());
        return token;
    }

    @Override
    public String refreshToken(String refreshToken, String clientId, String clientSecret) {
        ServiceInstance serviceInstance = loadBalancerClient.choose(CloudServiceInfo.AUTH_SERVER_AUTHENTICATION);
        URI uri = serviceInstance.getUri();
        String tokenUrl = uri + "/oauth/token";
        // get username
        final String username = redisTemplate.opsForValue().get(refreshToken);
        // 持久化的redis
        HttpHeaders headers = new HttpHeaders();
        headers.setBasicAuth(clientId, clientSecret);
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        // 设置请求体
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("grant_type", "refresh_token");
        map.add("refresh_token", refreshToken);
        // 用HttpEntity封装整个请求报文
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
        ResponseEntity<AccessToken> response = restTemplate.exchange(tokenUrl, HttpMethod.POST, request, AccessToken.class);
        String token = response.getBody().getAccess_token();
        redisTemplate.opsForValue().set(token, username);
        return token;
    }

    /**
     * url对应资源与用户拥有资源进行匹配
     *
     * @param attributes url对应的访问角色
     * @param roleNames  用户角色
     * @return
     */
    private boolean isMatch(List<String> attributes, Set<String> roleNames) {
        return roleNames.stream().anyMatch(resource -> attributes.contains(resource));
    }

}
